package com.ecoolex.ying.space.core.service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.aliyun.opensearch.OpenSearchClient;
import com.aliyun.opensearch.SearcherClient;
import com.aliyun.opensearch.sdk.generated.OpenSearch;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchClientException;
import com.aliyun.opensearch.sdk.generated.commons.OpenSearchException;
import com.aliyun.opensearch.sdk.generated.search.*;
import com.aliyun.opensearch.sdk.generated.search.general.SearchResult;
import com.aliyun.opensearch.search.SearchParamsBuilder;
import com.aliyun.opensearch.search.SearchResultDebug;
import com.ecoolex.framework.common.exception.RunningException;
import com.ecoolex.framework.common.pojo.Page;
import com.ecoolex.framework.common.pojo.Pagination;
import com.ecoolex.framework.common.util.Check;
import com.ecoolex.framework.common.util.RunTimingUtil;
import com.ecoolex.framework.common.util.StringUtil;
import com.ecoolex.ying.space.core.manager.UserWorksManager;
import com.ecoolex.ying.space.entity.UserWorks;
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.stream.Collectors;

@Component
public class UserWorksSearchService {

    private Logger log = LoggerFactory.getLogger(this.getClass());

    @Value("${aliyun.opensearch.access.id}")
    private String accesskey;

    @Value("${aliyun.opensearch.access.secret}")
    private String secret;

    @Value("${aliyun.opensearch.host}")
    private String host;

    @Value("${aliyun.opensearch.userwork.appname}")
    private String appName;

    private OpenSearchClient serviceClient;

    @PostConstruct
    private void initOpenSearch() {
        OpenSearch openSearch = new OpenSearch(accesskey, secret, host);
        //创建OpenSearchClient对象，并以OpenSearch对象作为构造参数
        serviceClient = new OpenSearchClient(openSearch);
    }

    @Autowired
    private UserWorksManager userWorksManager;

    public Pagination<UserWorks> searchByKeyword(String keyword, Page page) {
        return RunTimingUtil.timing(log,
                log.isDebugEnabled(),
                () -> RunningException.wrap(() -> searchByKeyword2(keyword, page), "搜索用户作品服务异常"),
                "搜索用户作品服务");
    }

    private String buildQueryString(String keyword) {
        if (Check.notNullOrTrimEmpty(keyword)) {
            StringBuffer sb = new StringBuffer();
            List<String> orKeyword = com.google.common.collect.Lists.newArrayList();
            String[] strArr = keyword.split("\\s+");
            for (String k : strArr) {
                if (Check.notTrimEmpty(k)) {
                    orKeyword.add("default:'" + StringUtil.trim(k) + "'");
                }
            }
            sb.append(StringUtil.joint(orKeyword, " OR "));
            return sb.toString();
        } else  {
            return null;
        }
    }

    private Pagination<UserWorks> searchByKeyword2(String keyword, Page page) throws OpenSearchClientException, OpenSearchException {
        //创建SearcherClient对象，并以OpenSearchClient对象作为构造参数
        SearcherClient client = new SearcherClient(serviceClient);
        //定义Config对象，用于设定config子句参数，分页或数据返回格式，指定应用名等等
        Config config = new Config(Lists.newArrayList(appName));
        config.setStart(page.getFirstIndex());
        config.setHits(page.getPageSize());
        //设置返回格式为FULLJSON，目前支持返回 XML，JSON，FULLJSON 等格式
        config.setSearchFormat(SearchFormat.FULLJSON);
        // 设置搜索结果返回应用中哪些字段
        config.setFetchFields(Lists.newArrayList("worksid"));
        // 注意：config子句中的rerank_size参数，在Rank类对象中设置
        //设置Kvpairs子句参数,此处为distinct子句添加uniq插件，用于reserverd=false时，total及viewtotal不准确问题
        config.setKvpairs("duniqfield:worksid");
        // 创建参数对象
        SearchParams searchParams = new SearchParams(config);
        // 设置查询子句，若需多个索引组合查询，需要setQuery处合并，否则若设置多个setQuery后面的会替换前面查询
        searchParams.setQuery(buildQueryString(keyword));
        // 设置聚合打散子句
        Distinct dist = new Distinct();
        dist.setKey("worksid"); //设置dist_key
        dist.setDistCount(1); //设置dist_count
        dist.setDistTimes(1); //设置dist_times
        dist.setReserved(false); //设置reserved
        dist.setUpdateTotalHit(true); //设置update_total_hit
        //此处duniqfield参数通过在config子句中指定kvpairs子句形式添加
        //添加Distinct对象参数
        searchParams.addToDistincts(dist);

        // 设置查询过滤条件
        // searchParams.setFilter("id > \"0\""); //此处也可改用后面的ParamsBuilder实现添加过滤条件
        // 设置sort条件
//        Sort sorter = new Sort();
//        sorter.addToSortFields(new SortField("worksid", Order.DECREASE)); //设置id字段降序
//        //添加Sort对象参数
//        searchParams.setSort(sorter);
        // 设置粗精排表达式，此处设置为默认
        Rank rank = new Rank();
        rank.setFirstRankName("default");
        rank.setSecondRankName("default");
        rank.setReRankSize(page.getPageSize());//设置参与精排文档个数
        searchParams.setRank(rank);

        SearchParamsBuilder paramsBuilder = SearchParamsBuilder.create(searchParams);
        // 设置查询过滤条件
//        paramsBuilder.addFilter("worksid>=0", "AND");
//        paramsBuilder.addFilter("deleted=0", "AND");
        // 执行返回查询结果。用户需按code和message，进行异常情况判断。code对应的错误信息查看——错误码文档。
        SearchResult searchResult = client.execute(paramsBuilder);
        log.debug("搜索用户作品结果: {}", searchResult);
        if (log.isDebugEnabled()) {
            //输出上次查询请求串信息
            SearchResultDebug searchdebugrst = client.executeDebug(searchParams);
            log.debug("搜索用户作品查询数据: {}", JSON.toJSONString(searchdebugrst));
        }

        JSONObject resultJson = JSON.parseObject(searchResult.getResult());

        JSONObject result = resultJson.getJSONObject("result");
        Integer total = result.getInteger("total");

        List<Integer> worksidList = result.getJSONArray("items")
                .stream()
                .map(JSONObject.class::cast)
                .map(obj -> obj.getJSONObject("fields").getInteger("worksid"))
                .collect(Collectors.toList());

        return Pagination.create(page, total, () -> {
            // 还原结果顺序
            Map<Integer, UserWorks> mapResult = userWorksManager.queryByWorksIds(worksidList).stream()
                    .collect(Collectors.toMap(UserWorks::getWorksId, self -> self));
            return worksidList.stream()
                    .map(worksid -> mapResult.get(worksid))
                    .collect(Collectors.toList());
        });
    }
}
